﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace TICC2480MTClient
{
    public class SerialPacket
    {
        public byte CMD0 { get; set; }
        public byte CMD1 { get; set; }
        public byte[] Data { get; set; }

        public string DataAsHex
        {
            get {
                StringBuilder sb = new StringBuilder(Data.Length * 3);
                foreach (byte b in Data)
                {
                    sb.Append(b.ToString("X2"));
                    sb.Append(" ");
                }
                return sb.ToString(0, sb.Length - 1);
            }
        }

        public string Command
        {
            get
            {
                try
                {
                    if (CMD0 == 0x46 && CMD1 == 0x87)
                    {
                        if (Data.Length < 6)
                            return "ZB_RECEIVE_DATA_INDICATION, bad length=" + Data.Length;

                        int source = Data[0];
                        source += ((int)Data[1]) << 8;
                        int command = Data[2];
                        command += ((int)Data[3]) << 8;
                        int length = Data[4];
                        length += ((int)Data[5]) << 8;

                        if (Data.Length < 6 + length)
                            return "ZB_RECEIVE_DATA_INDICATION, bad packet length=" + length;

                        StringBuilder sb = new StringBuilder(length * 3);
                        for (int i = 0; i < length; i++)
                        {

                            sb.Append(Data[6 + i].ToString("X2"));
                            sb.Append(" ");
                        }
                        string dataString = sb.ToString(0, sb.Length - 1);

                        string res = string.Format("ZB_RECEIVE_DATA_INDICATION (source={0:X4}, command={1:X4}, length={2}, data={3}",
                            source, command, length, dataString);

                        return res;
                    }
                    else if (CMD0 == 0x46)
                    {
                        if (CMD1 == 0x80)
                        {
                            if (Data.Length < 1)
                                return "ZB_START_CONFIRM, bad length=" + Data.Length;

                            return string.Format("ZB_START_CONFIRM (status={0:X2})", Data[0]);
                        }
                        if (CMD1 == 0x81)
                        {
                            if (Data.Length < 3)
                                return "ZB_BIND_CONFIRM, bad length=" + Data.Length;

                            int command = Data[0];
                            command += ((int)Data[1]) << 8;
                            return string.Format("ZB_BIND_CONFIRM (command={0:X4}, status={1:X2})", command, Data[2]);
                        }
                        if (CMD1 == 0x82)
                        {
                            if (Data.Length < 2)
                                return "ZB_ALLOW_BIND_CONFIRM, bad length=" + Data.Length;

                            int source = Data[0];
                            source += ((int)Data[1]) << 8;
                            return string.Format("ZB_ALLOW_BIND_CONFIRM (source={0:X4})", source);
                        }
                        if (CMD1 == 0x83)
                        {
                            if (Data.Length < 2)
                                return "ZB_SEND_DATA_CONFIRM, bad length=" + Data.Length;

                            return string.Format("ZB_SEND_DATA_CONFIRM (handle={0:X2}, status={1:X2})", Data[0], Data[1]);
                        }
                    }
                    else if (CMD0 == 0x41 && CMD1 == 0x80)
                    {
                        if (Data.Length < 6)
                            return "SYS_RESET_IND, bad length=" + Data.Length;
                        return string.Format("SYS_RESET_IND");
                    }
                    else if (CMD0 == 0x66)
                    {
                        if (CMD1 == 0x05)
                        {
                            if (Data.Length < 1)
                                return "ZB_WRITE_CONFIGURATION_SRSP, bad length=" + Data.Length;
                            int status = Data[0];
                            return string.Format("ZB_WRITE_CONFIGURATION_SRSP (status={0:X2})", status);
                        }
                        if (CMD1 == 0x0A)
                        {
                            if (Data.Length < 1)
                                return "ZB_APP_REGISTER_REQUEST_SRSP, bad length=" + Data.Length;
                            int status = Data[0];
                            return string.Format("ZB_APP_REGISTER_REQUEST_SRSP (status={0:X2})", status);
                        }
                        if (CMD1 == 0x08)
                        {
                            if (Data.Length < 1)
                                return "ZB_PERMIT_JOIN_REQUEST_SRSP, bad length=" + Data.Length;
                            int status = Data[0];
                            return string.Format("ZB_PERMIT_JOIN_REQUEST_SRSP (status={0:X2})", status);
                        }
                        if (CMD1 == 0x06)
                        {
                            if (Data.Length < 9)
                                return "ZB_GET_DEVICE_INFO_SRSP, bad length=" + Data.Length;
                            int param = Data[0];
                            return string.Format("ZB_GET_DEVICE_INFO_SRSP (param={0:X2} value={1:X2} {2:X2} {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2})",
                                param, Data[1], Data[2], Data[3], Data[4], Data[5], Data[6], Data[7], Data[8]);

                        }
                    }
                    else if (CMD0 == 0x45)
                    {
                        if (CMD1 == 0xB6)
                        {
                            if (Data.Length < 3)
                                return "ZDO_MGMT_PERMIT_JOIN_RSP, bad length=" + Data.Length;
                            int source = Data[0] + (int)Data[1] << 8;
                            int status = Data[2];
                            return string.Format("ZDO_MGMT_PERMIT_JOIN_RSP (source={0:X4} status={1:X2})", source, status);
                        }
                    }
                    return "Unknown";
                }
                catch (Exception ex)
                {
                    return ex.ToString();
                }
            }
        }

        private static byte msgctr = 0;

        public static SerialPacket CreateSendDataRequestCommand(
            ushort destination, ushort commandId, 
            /*byte handle, */ bool ack, byte radius, byte[] data)
        {
            SerialPacket p = new SerialPacket()
            {
                CMD0 = 0x26,
                CMD1 = 0x03,
                Data = new byte[data.Length+8],
            };
            p.Data[0] = (byte)(destination&0xFF);
            p.Data[1] = (byte)((destination>>8)&0xFF);

            p.Data[2] = (byte)(commandId&0xFF);
            p.Data[3] = (byte)((commandId>>8)&0xFF);

            p.Data[4] = msgctr++;

            p.Data[5] = (byte)(ack?1:0);
            p.Data[6] = radius;
            p.Data[7] = (byte)data.Length;

            for(int i=0; i<data.Length; i++)
                p.Data[8+i] = data[i];

            return p;

        }

        public static SerialPacket CreateAllowBindCommand(byte timeout)
        {
            SerialPacket p = new SerialPacket()
            {
                CMD0 = 0x26,
                CMD1 = 0x02,
                Data = new byte[1],
            };
            p.Data[0] = (byte)timeout;
            return p;

        }

        public static SerialPacket CreateBindDeviceCommand(bool create, ushort commandId, byte[] destinationAddr)
        {
            SerialPacket p = new SerialPacket()
            {
                CMD0 = 0x26,
                CMD1 = 0x01,
                Data = new byte[11],
            };

            if (destinationAddr == null)
            {
                p.Data.Initialize();
            }
            else if (destinationAddr.Length == 8)
                for (int i = 0; i < 8; i++)
                    p.Data[3+i] = destinationAddr[i];
            p.Data[0] = (byte)(create ? 1 : 0);
            p.Data[1] = (byte)(commandId & 0xFF);
            p.Data[2] = (byte)((commandId>>8) & 0xFF);
            return p;
        }
    }

    public class PacketEventArgs : EventArgs
    {
        public SerialPacket Packet { get; set; }
    }

    public class MTSerialClient
    {
        public static readonly byte SOP_VALUE = 0xFE;

        public event EventHandler<PacketEventArgs> CommandReceived;
        

        Stream st = null;
        public MTSerialClient(Stream stream)
        {
            st = stream;

            Thread t = new Thread(ReadThread);
            t.IsBackground = true;
            t.Start();
        }


        public void SendCommand(SerialPacket p)
        {
            byte[] bytes = new byte[p.Data.Length];
            lock (this)
            {
                st.WriteByte(SOP_VALUE);
                st.WriteByte((byte)p.Data.Length);
                st.WriteByte(p.CMD0);
                st.WriteByte(p.CMD1);
                st.Write(p.Data, 0, p.Data.Length);
                int cfcs = calcFCS(p.Data) ^ p.Data.Length ^ p.CMD0 ^ p.CMD1;
                st.WriteByte((byte)cfcs);
            }
        }

        private void ReadThread()
        {
            while (true)
            {
                try
                {
                    int b;
                    do
                    {
                        b = st.ReadByte();    // Start of packet
                    } while (b != SOP_VALUE);

                    int len = st.ReadByte();  // Length of data
                    int cmd0 = st.ReadByte(); // CMD0
                    int cmd1 = st.ReadByte(); // CMD1
                    if (len < 0)
                    {
                        continue;
                    }
                    byte[] data = new byte[len];
                    for (int i = 0; i < len; i++) //data
                    {
                        data[i] = (byte)st.ReadByte();
                    }

                    int cfcs = calcFCS(data) ^ len ^ cmd0 ^ cmd1;
                    int rfcs = st.ReadByte(); //FCS

                    if (rfcs == cfcs)
                    {
                        var p = new SerialPacket()
                        {
                            CMD0 = (byte)cmd0,
                            CMD1 = (byte)cmd1,
                            Data = data
                        };
                        if (CommandReceived != null)
                            CommandReceived(this, new PacketEventArgs() { Packet = p });
                    }
                }
                catch(Exception e)
                {

                }
            }
        }

        private int calcFCS(byte[] data)
        {
            int rtrn = 0;
            foreach(byte b in data)
            {
                rtrn ^= b;
            }
            return rtrn;
        }

    }
}
